package com.aiyi.server.manager.nginx.common;

import com.aiyi.server.manager.nginx.conf.Configer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * WebSocket获取实时日志并输出到Web页面
 */

@Slf4j
@Component
@ServerEndpoint(value = "/websocket/logging/{logFlag}")
public class LoggingWSServerUtils {

    /**
     * 链接集合
     */
    private static Map<String, Session> sessionMap = new ConcurrentHashMap<>();
    private static Map<String, Long> indexMap = new ConcurrentHashMap<>();

    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("logFlag") String logFlag) {
        //session集合
        sessionMap.put(session.getId(), session);
        //指针位置，默认0
        indexMap.put(session.getId(), (long) 0);

        //获取日志信息
        new Thread(() -> {
            log.info("LoggingWebSocketServer 任务开始");
            boolean first = true;
            while (sessionMap.get(session.getId()) != null) {
                RandomAccessFile rf = null;
                try {
                    //日志文件路径，获取最新的
                    String nginxPath = Configer.getNginxPath();
                    //0-access日志 1-error日志
                    String filePath = nginxPath + "/logs/" + ("0".equals(logFlag)? "access.log" : "error.log");

                    rf = new RandomAccessFile(filePath, "r");
                    //第一次读只取前200行
                    if (first){
                        String firstResult = readLastRows(rf, null, 200);
                        first = false;
                        indexMap.put(session.getId(), rf.getFilePointer());
                        //发送
                        send(session, firstResult);
                        Thread.sleep(3000);
                        continue;
                    }

                    //只取从上次之后产生的日志
                    //指针移动至上次指针存储的位置
                    rf.seek(indexMap.get(session.getId()));
                    //只取从上次之后产生的日志
                    String s;
                    StringBuffer sb = new StringBuffer();
                    while ( (s = rf.readLine()) != null){
                        sb.append(handleLine(s) + "<br/>");
                    }
                    //更新指针位置
                    indexMap.put(session.getId(), rf.getFilePointer());

                    //发送
                    send(session, sb.toString());
                    //休眠3秒
                    Thread.sleep(3000);
                } catch (Exception e) {
                    //捕获但不处理
                    e.printStackTrace();
                } finally {
                    if(rf != null){
                        try {
                            rf.close();
                        } catch (IOException ignored) {
                        }
                    }
                }
            }
            log.info("LoggingWebSocketServer 任务结束");
        }).start();
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose(Session session) {
        //从集合中删除
        sessionMap.remove(session.getId());
        indexMap.remove(session.getId());
    }

    /**
     * 发生错误时调用
     */
    @OnError
    public void onError(Session session, Throwable error) {
        error.printStackTrace();
    }

    /**
     * 服务器接收到客户端消息时调用的方法
     */
    @OnMessage
    public void onMessage(String message, Session session) {

    }

    /**
     * 封装一个send方法，发送消息到前端
     */
    private void send(Session session, String message) {
        try {
            session.getBasicRemote().sendText(message);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 读取文件指定的后几行
     * @param rf
     * @param charset
     * @param rows
     * @return
     * @throws IOException
     */
    private String readLastRows(RandomAccessFile rf, Charset charset, int rows) throws IOException {
        charset = charset == null ? Charset.defaultCharset() : charset;
        byte[] lineSeparator = System.getProperty("line.separator").getBytes();
        // 每次读取的字节数要和系统换行符大小一致
        byte[] c = new byte[lineSeparator.length];
        // 在获取到指定行数和读完文档之前,从文档末尾向前移动指针,遍历文档每一个字节
        for (long pointer = rf.length(), lineSeparatorNum = 0; pointer >= 0 && lineSeparatorNum < rows;) {
            // 移动指针
            rf.seek(pointer--);
            // 读取数据
            int readLength = rf.read(c);
            if (readLength != -1 && Arrays.equals(lineSeparator,c)) {
                lineSeparatorNum++;
            }
            //扫描完依然没有找到足够的行数,将指针归0
            if (pointer == -1 && lineSeparatorNum < rows) {
                rf.seek(0);
            }
        }
//            byte[] tempbytes = new byte[(int) (rf.length() - rf.getFilePointer())];
//            rf.readFully(tempbytes);

        //逐行处理指针之后的数据，用于页面友好显示
        String s;
        StringBuffer sb = new StringBuffer();
        while ( (s = rf.readLine()) != null){
            sb.append(handleLine(s) + "<br/>");
        }
        return sb.toString();
    }

    /**
     * 行样式处理
     * @param line
     * @return
     */
    private String handleLine(String line){
        line = line.replaceAll("&", "&amp;")
                .replaceAll("<", "&lt;")
                .replaceAll(">", "&gt;")
                .replaceAll("\"", "&quot;");

        //处理等级
        line = line.replace("DEBUG", "<span style='color: blue;'>DEBUG</span>");
        line = line.replace("debug", "<span style='color: blue;'>debug</span>");
        line = line.replace("INFO", "<span style='color: green;'>INFO</span>");
        line = line.replace("info", "<span style='color: green;'>info</span>");
        line = line.replace("WARN", "<span style='color: orange;'>WARN</span>");
        line = line.replace("warn", "<span style='color: orange;'>warn</span>");
        line = line.replace("ERROR", "<span style='color: red;'>ERROR</span>");
        line = line.replace("error", "<span style='color: red;'>error</span>");

        return line;
    }
}